package com.mornd.gulimall.ware.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mornd.gulimall.apis.order.IOrderClient;
import com.mornd.gulimall.apis.order.to.OrderTo;
import com.mornd.gulimall.apis.ware.to.*;
import com.mornd.gulimall.common.core.constant.OrderStatus;
import com.mornd.gulimall.common.core.exception.ServiceException;
import com.mornd.gulimall.common.core.result.R;
import com.mornd.gulimall.ware.entity.WareInfo;
import com.mornd.gulimall.ware.entity.WareOrderTask;
import com.mornd.gulimall.ware.entity.WareOrderTaskDetail;
import com.mornd.gulimall.ware.entity.WareSku;
import com.mornd.gulimall.ware.mapper.WareSkuMapper;
import com.mornd.gulimall.ware.service.IWareInfoService;
import com.mornd.gulimall.ware.service.IWareOrderTaskDetailService;
import com.mornd.gulimall.ware.service.IWareOrderTaskService;
import com.mornd.gulimall.ware.service.IWareSkuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import javax.annotation.Resource;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.util.StringUtils.hasText;

/**
 * <p>
 * 商品库存 服务实现类
 * </p>
 *
 * @author mornd
 * @since 2023-07-29
 */
@Slf4j
@Service
public class WareSkuServiceImpl extends ServiceImpl<WareSkuMapper, WareSku> implements IWareSkuService {
    @Resource
    private IWareInfoService wareInfoService;
    @Resource
    private IWareOrderTaskService wareOrderTaskService;
    @Resource
    private IWareOrderTaskDetailService wareOrderTaskDetailService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private IOrderClient orderClient;

    @Override
    public IPage<WareSku> list(WareSku wareSku) {
        IPage<WareSku> page = new Page<>(wareSku.validPageNum(), wareSku.validPageSize());

        LambdaQueryWrapper<WareSku> qw = Wrappers.<WareSku>lambdaQuery();
        qw.eq(wareSku.getWareId() != null, WareSku::getWareId, wareSku.getWareId());
        qw.eq(hasText(wareSku.getSkuId()), WareSku::getSkuId, wareSku.getSkuId());
        qw.like(hasText(wareSku.getSkuName()), WareSku::getSkuName, wareSku.getSkuName());
        super.page(page, qw);

        for (WareSku record : page.getRecords()) {
            WareInfo ware = wareInfoService.getById(record.getWareId());
            if (ware != null) {
                record.setWareName(ware.getName());
            }
        }
        return page;
    }

    @Override
    public List<SkusHasStockTo> getSkusHasStock(List<Long> skuIds) {
        List<SkusHasStockTo> coll = skuIds.stream().map(skuId -> {
            SkusHasStockTo hasStockTo = new SkusHasStockTo();
            Long stockCount = baseMapper.getSkuStock(skuId);
            hasStockTo.setSkuId(skuId);
            hasStockTo.setHasStock(stockCount != null && stockCount > 0);
            return hasStockTo;
        }).collect(Collectors.toList());
        return coll;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderLockStockResult orderLockStock(WareSkuLockTo wareSkuLockTo) {
        OrderLockStockResult result = new OrderLockStockResult();
        boolean fail = false;
        Long failSkuId = null;

        // 保存库存工作单
        WareOrderTask wareOrderTask = new WareOrderTask();
        wareOrderTask.setOrderSn(wareSkuLockTo.getOrderSn());
        wareOrderTask.setCreateTime(LocalDateTime.now());
        wareOrderTaskService.save(wareOrderTask);

        for (WareSkuLockTo.SkuInfo lockSkus : wareSkuLockTo.getSkuInfo()) {
            List<WareSku> lockableStockList = baseMapper.getLockableStock(lockSkus.getSkuId(), lockSkus.getCount());
            if (lockableStockList.isEmpty()) {
                fail = true;
                failSkuId = lockSkus.getSkuId();
                break;
            }

            WareSku wareSku = lockableStockList.get(0);

            int row = baseMapper.lockSkuStockById(wareSku.getId(), lockSkus.getSkuId(), lockSkus.getCount());
            if (row == 0) {
                // 锁定失败
                fail = true;
                failSkuId = lockSkus.getSkuId();
                break;
            }

            // 每锁定一个就发一条消息，如果出现异常，数据库会回滚，库存工作单也就查不到了，所以mq不需要回滚
            WareOrderTaskDetail wareOrderTaskDetail = new WareOrderTaskDetail();
            wareOrderTaskDetail.setSkuId(lockSkus.getSkuId());
            wareOrderTaskDetail.setSkuNum(lockSkus.getCount());
            wareOrderTaskDetail.setWareSkuId(wareSku.getId());
            wareOrderTaskDetail.setWareId(wareSku.getWareId());
            wareOrderTaskDetail.setTaskId(wareOrderTask.getId());
            wareOrderTaskDetail.setLockStatus(1);
            wareOrderTaskDetail.setCreateTime(LocalDateTime.now());
            wareOrderTaskDetailService.save(wareOrderTaskDetail);

            // 通知mq消息锁定成功
            MqStockLockedTo mqStockLockedTo = new MqStockLockedTo();
            mqStockLockedTo.setId(wareOrderTask.getId());
            mqStockLockedTo.setOrderSn(wareSkuLockTo.getOrderSn());
            WareOrderTaskDetailTo detailTo = new WareOrderTaskDetailTo();
            BeanUtils.copyProperties(wareOrderTaskDetail, detailTo);
            mqStockLockedTo.setDetail(detailTo);
            rabbitTemplate.convertAndSend("stock-event-exchange",
                    "stock.locked",
                    mqStockLockedTo);

            // 封装结果
            OrderLockStockResult.Sku successSkus = new OrderLockStockResult.Sku();
            successSkus.setSkuId(lockSkus.getSkuId());
            successSkus.setStockId(wareSku.getId());
            result.getSkus().add(successSkus);
        }

        if (fail) {
            result.setLocked(false);
            result.setFailSkuId(failSkuId);
            result.setFailMsg("skuId=" + failSkuId + "的库存不足");
            TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
            return result;
        }

        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean orderLockStock2(WareSkuLockTo wareSkuLockTo) {
        for (WareSkuLockTo.SkuInfo orderLockTo : wareSkuLockTo.getSkuInfo()) {
            int row = baseMapper.lockSkuStock(orderLockTo.getSkuId(), orderLockTo.getCount());
            if (row == 0) {
                throw new ServiceException("库存锁定失败，" + orderLockTo.getSkuId() + "的库存不足");
            }
        }
        return true;
    }

    /**
     * 解锁库存
     *
     * @param to
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlock(MqStockLockedTo to) {
        String orderSn = to.getOrderSn();
        WareOrderTaskDetailTo detail = to.getDetail();

        // 幂等性处理，查询订单表是否存在记录，如果存在记录表示正确的扣减了库存
        WareOrderTaskDetail dbDetail = wareOrderTaskDetailService.getById(detail.getId());
        if (dbDetail != null && dbDetail.getLockStatus() == 1) { // 已锁定未解锁状态
            // 还要判断订单是否存在，状态是否是取消
            R<OrderTo> r = orderClient.getOrderBySn(orderSn);
            OrderTo orderTo = r.getData();
            if (r.isFail()) {
                // 重新入队
                throw new ServiceException("远程调用订单服务失败，" + r.getMsg());
            }

            if (orderTo == null || OrderStatus.CANCLED.getCode() == orderTo.getStatus()) {
                // 解锁
                log.debug("开始解锁库存");
                WareOrderTaskDetail wrapper = new WareOrderTaskDetail();
                wrapper.setId(detail.getId());
                wrapper.setLockStatus(2);
                wrapper.setUpdateTime(LocalDateTime.now());
                wareOrderTaskDetailService.updateById(wrapper);
                baseMapper.unlock(detail.getWareSkuId(), detail.getSkuNum());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlock(OrderTo orderTo) {
        if (OrderStatus.CANCLED.getCode() == orderTo.getStatus()) {
            WareOrderTask orderTask = wareOrderTaskService.getOne(Wrappers.<WareOrderTask>lambdaQuery().eq(WareOrderTask::getOrderSn, orderTo.getOrderSn()));
            if (orderTask != null) {
                LambdaQueryWrapper<WareOrderTaskDetail> qw = Wrappers.<WareOrderTaskDetail>lambdaQuery().eq(WareOrderTaskDetail::getTaskId, orderTask.getId())
                        .eq(WareOrderTaskDetail::getLockStatus, 1);
                List<WareOrderTaskDetail> list = wareOrderTaskDetailService.list(qw);
                for (WareOrderTaskDetail detail : list) {
                    WareOrderTaskDetail wrapper = new WareOrderTaskDetail();
                    wrapper.setId(detail.getId());
                    wrapper.setLockStatus(2);
                    wrapper.setUpdateTime(LocalDateTime.now());
                    wareOrderTaskDetailService.updateById(wrapper);
                    baseMapper.unlock(detail.getWareSkuId(), detail.getSkuNum());
                }
            }
        }
    }
}
